﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Loki.Commands;
using Loki.Utils;

namespace Loki.UI
{
    /// <summary>
    /// Base class for viewmodels.
    /// </summary>
    /// <typeparam name="TModel">The type of the model.</typeparam>
    public abstract class ViewModel : EditableViewModel, ICommandAware, IViewModel
    {
        protected ViewModel()
        {
            try
            {
                Commands = new CommandManager(this);
                ErrorType = ValidationMode.Information;
                RegisterDisposable(Commands);

                Close = Commands.Create(true);
                Activate = Commands.Create(true);
                Desactivate = Commands.Create(true);

                Commands.Handle(Activate, Activate_Execute);
                Commands.Handle(Close, Close_Execute);
                Commands.Handle(Desactivate, Desctivate_Execute);
            }
            catch
            {
                if (Commands != null)
                {
                    Commands.Dispose();
                }

                throw;
            }
        }

        #region IsInitialized

        private static PropertyChangedEventArgs _IsInitializedChangedArgs = ObservableHelper.CreateChangedArgs<ViewModel>(x => x.IsInitialized);

        private static PropertyChangingEventArgs _IsInitializedChangingArgs = ObservableHelper.CreateChangingArgs<ViewModel>(x => x.IsInitialized);

        private bool _IsInitialized;

        public bool IsInitialized
        {
            get
            {
                return _IsInitialized;
            }

            set
            {
                if (value != _IsInitialized)
                {
                    NotifyChanging(_IsInitializedChangingArgs);
                    _IsInitialized = value;
                    NotifyChanged(_IsInitializedChangedArgs);
                }
            }
        }

        #endregion IsInitialized

        #region IsActive

        private static PropertyChangedEventArgs _IsActiveChangedArgs = ObservableHelper.CreateChangedArgs<ViewModel>(x => x.IsActive);

        private static PropertyChangingEventArgs _IsActiveChangingArgs = ObservableHelper.CreateChangingArgs<ViewModel>(x => x.IsActive);

        private bool _IsActive;

        public virtual bool IsActive
        {
            get
            {
                return _IsActive;
            }

            set
            {
                if (value != _IsActive)
                {
                    NotifyChanging(_IsActiveChangingArgs);
                    _IsActive = value;
                    NotifyChanged(_IsActiveChangedArgs);
                }
            }
        }

        #endregion IsActive

        #region DisplayName

        private static PropertyChangedEventArgs _DisplayNameChangedArgs = ObservableHelper.CreateChangedArgs<ViewModel>(x => x.DisplayName);

        private static PropertyChangingEventArgs _DisplayNameChangingArgs = ObservableHelper.CreateChangingArgs<ViewModel>(x => x.DisplayName);

        private string _DisplayName;

        public string DisplayName
        {
            get
            {
                return _DisplayName;
            }

            set
            {
                if (value != _DisplayName)
                {
                    NotifyChanging(_DisplayNameChangingArgs);
                    _DisplayName = value;
                    NotifyChanged(_DisplayNameChangedArgs);
                }
            }
        }

        #endregion DisplayName

        #region FullDisplayName

        public virtual string FullDisplayName
        {
            get
            {
                return DisplayName;
            }
        }

        #endregion FullDisplayName

        public bool CancelEditConfirm(CommandEventArgs e)
        {
            if (IsChanged)
            {
                using (var L_Model = ConfirmViewModel.Confirm(Messages.VMMessages.CONFIRM_EDIT_LOSS))
                {
                    return ShowAsModal(L_Model) ?? false;
                }
            }
            else
            {
                return true;
            }
        }

        #region Modal child

        private static PropertyChangedEventArgs _ModalViewModelChangedArgs = ObservableHelper.CreateChangedArgs<ViewModel>(x => x.ModalViewModel);

        private static PropertyChangingEventArgs _ModalViewModelChangingArgs = ObservableHelper.CreateChangingArgs<ViewModel>(x => x.ModalViewModel);

        private IModalViewModel _ModalViewModel;

        public IModalViewModel ModalViewModel
        {
            get
            {
                return _ModalViewModel;
            }

            private set
            {
                if (value != _ModalViewModel)
                {
                    if (value == null)
                    {
                        Activate.Execute(null);
                    }
                    else
                    {
                        Desactivate.Execute(null);
                    }

                    NotifyChanging(_ModalViewModelChangingArgs);
                    _ModalViewModel = value;
                    NotifyChanged(_ModalViewModelChangedArgs);
                }
            }
        }

        protected bool? ShowAsModal(IModalViewModel P_ViewModel)
        {
            ModalViewModel = P_ViewModel;
            ModalViewModel = null;
            return P_ViewModel.DialogResult;
        }

        #endregion Modal child

        #region Commands

        /// <summary>
        /// Gets the activate command.
        /// </summary>
        public ICommand Activate { get; private set; }

        /// <summary>
        /// Gets the close command.
        /// </summary>
        public ICommand Close { get; private set; }

        /// <summary>
        /// Gets the desactivate command.
        /// </summary>
        public ICommand Desactivate { get; private set; }

        protected CommandManager Commands { get; private set; }

        #endregion Commands

        #region Message handling

        protected void HandleError(string P_Error, params object[] P_Params)
        {
            ApplicationCommands.ERROR.Execute(string.Format(CultureInfo.InvariantCulture, P_Error, P_Params));
        }

        protected void HandleException(Exception P_Ex)
        {
            ApplicationCommands.ERROR.Execute(P_Ex);
        }

        protected void UserMessage(string P_Message, params object[] P_Params)
        {
            ApplicationCommands.MESSAGE.Execute(string.Format(CultureInfo.InvariantCulture, P_Message, P_Params));
        }

        protected void UserWarning(string P_Warning, params object[] P_Params)
        {
            ApplicationCommands.WARNING.Execute(string.Format(CultureInfo.InvariantCulture, P_Warning, P_Params));
        }

        #endregion Message handling

        #region Background workers

        private HashSet<string> _RunningProcess = new HashSet<string>();

        protected Action<TArgument> CreateWorker<TArgument, TResult>(string P_Title, Func<TArgument, TResult> P_DoWork, Action<TResult> P_OnComplete)
        {
            return CreateWorker<TArgument, TResult>(P_Title, P_DoWork, P_OnComplete, HandleException);
        }

        protected Action<TArgument> CreateWorker<TArgument, TResult>(string P_Title, Func<TArgument, TResult> P_DoWork, Action<TResult> P_OnComplete, Action<Exception> P_OnError)
        {
            string L_TaskDisplayTitle = string.Format(CultureInfo.InvariantCulture, "{0} : {1}", FullDisplayName, P_Title);
            ITask<TArgument> L_Worker = ToolKit.Task.CreateWorker<TArgument, TResult>(L_TaskDisplayTitle, () => BeginBackgroudWork(P_Title), P_DoWork, (r) => { EndBackgroudWork(P_Title); P_OnComplete(r); }, (e) => { EndBackgroudWork(P_Title); P_OnError(e); });

            // insert for disposing
            RegisterDisposable(L_Worker);

            return L_Worker.Run;
        }

        protected Action CreateWorker(string P_Title, Action P_DoWork, Action P_OnComplete)
        {
            return CreateWorker(P_Title, P_DoWork, P_OnComplete, HandleException);
        }

        protected Action CreateWorker(string P_Title, Action P_DoWork, Action P_OnComplete, Action<Exception> P_OnError)
        {
            string L_TaskDisplayTitle = string.Format(CultureInfo.InvariantCulture, "{0} : {1}", FullDisplayName, P_Title);
            ITask L_Worker = ToolKit.Task.CreateWorker(L_TaskDisplayTitle, () => { BeginBackgroudWork(P_Title); P_DoWork(); }, () => { EndBackgroudWork(P_Title); P_OnComplete(); }, (e) => { EndBackgroudWork(P_Title); P_OnError(e); });

            // insert for disposing
            RegisterDisposable(L_Worker);

            return L_Worker.Run;
        }

        #region Status

        private static PropertyChangedEventArgs _StatusChangedArgs = ObservableHelper.CreateChangedArgs<ViewModel>(x => x.Status);

        private static PropertyChangingEventArgs _StatusChangingArgs = ObservableHelper.CreateChangingArgs<ViewModel>(x => x.Status);

        private string _Status;

        public string Status
        {
            get
            {
                return _Status;
            }

            set
            {
                if (value != _Status)
                {
                    NotifyChanging(_StatusChangingArgs);
                    _Status = value;
                    NotifyChanged(_StatusChangedArgs);
                }
            }
        }

        #endregion Status

        #region IsBusy

        private static PropertyChangedEventArgs _IsBusyChangedArgs = ObservableHelper.CreateChangedArgs<ViewModel>(x => x.IsBusy);

        private static PropertyChangingEventArgs _IsBusyChangingArgs = ObservableHelper.CreateChangingArgs<ViewModel>(x => x.IsBusy);

        private bool _IsBusy;

        public bool IsBusy
        {
            get
            {
                return _IsBusy;
            }

            set
            {
                if (value != _IsBusy)
                {
                    NotifyChanging(_IsBusyChangingArgs);
                    _IsBusy = value;
                    NotifyChanged(_IsBusyChangedArgs);
                }
            }
        }

        #endregion IsBusy

        private void BeginBackgroudWork(string P_Title)
        {
            Status = P_Title;
            IsBusy = true;
            _RunningProcess.Add(P_Title);
            ApplicationCommands.UPDATESTATUS.Execute(Status);
        }

        private void EndBackgroudWork(string P_Title)
        {
            if (_RunningProcess.Contains(P_Title))
            {
                _RunningProcess.Remove(P_Title);
            }

            if (_RunningProcess.Count > 0)
            {
                Status = _RunningProcess.First();
            }
            else
            {
                Status = string.Empty;
            }

            IsBusy = false;

            ApplicationCommands.UPDATESTATUS.Execute(Status);
        }

        #endregion Background workers

        #region Dispose

        private List<DisposableSet> disposables = new List<DisposableSet>();

        private List<Action> unbinders = new List<Action>();

        /// <summary>
        /// Releases the unmanaged resources used by an instance of the <see cref="ViewModel" />
        /// class and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">Set <strong>true</strong> to release both managed and
        /// unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                foreach (var item in disposables)
                {
                    if (item.Unbinder != null)
                    {
                        item.Unbinder(item.Item);
                    }

                    item.Item.Dispose();
                }

                disposables.Clear();

                foreach (var unbinder in unbinders)
                {
                    unbinder();
                }

                unbinders.Clear();

                // Dispose change notifications
                ToolKit.Event.UnregisterPropertyChangedSource(this);
                ToolKit.Event.UnregisterStateChangedSource(this);
                ToolKit.Event.UnregisterChangedSource(this);
            }
        }

        protected void RegisterDisposable(IDisposable P_Disposable)
        {
            RegisterDisposable(P_Disposable, null);
        }

        protected void RegisterDisposable(IDisposable P_Disposable, Action<IDisposable> P_Unbind)
        {
            disposables.Add(new DisposableSet()
            {
                Item = P_Disposable,
                Unbinder = P_Unbind
            });
        }

        private struct DisposableSet
        {
            public IDisposable Item;
            public Action<IDisposable> Unbinder;
        }

        #endregion Dispose

        #region Loading Process

        private object _CleanLock = new object();
        private object _InitializeLock = new object();

        private object _LoadLock = new object();

        private object _StartLock = new object();

        public bool Cleaned
        {
            get;
            private set;
        }

        public bool Loaded
        {
            get;
            private set;
        }

        public bool Started
        {
            get;
            private set;
        }

        public void DoCleanup()
        {
            lock (_CleanLock)
            {
                if (!Cleaned)
                {
                    OnClean(EventArgs.Empty);
                    ValidationEnabled = false;
                    IsInitialized = false;
                    Started = false;
                    Loaded = false;
                    Cleaned = true;
                    Dispose();
                }
            }
        }

        public void Initialize()
        {
            lock (_InitializeLock)
            {
                if (!IsInitialized && !Cleaned)
                {
                    BeginInit();
                    try
                    {
                        OnInit(EventArgs.Empty);
                    }
                    catch (Exception Ex)
                    {
                        HandleException(Ex);
                    }

                    EndInit(false);
                    IsInitialized = true;
                    OnInitialized(EventArgs.Empty);
                }
            }
        }

        public void LoadData()
        {
            lock (_LoadLock)
            {
                if (!Loaded && !Cleaned)
                {
                    BeginInit();
                    try
                    {
                        OnLoad(EventArgs.Empty);
                    }
                    catch (Exception Ex)
                    {
                        HandleException(Ex);
                    }

                    EndInit();
                    Loaded = true;
                }
            }
        }

        public void StartInteraction()
        {
            lock (_StartLock)
            {
                if (!Started && !Cleaned)
                {
                    try
                    {
                        ValidationEnabled = true;
                        OnStart(EventArgs.Empty);
                    }
                    catch (Exception Ex)
                    {
                        HandleException(Ex);
                    }

                    Started = true;
                }
            }
        }

        #region Events

        #region Clean

        public event EventHandler Clean;

        protected virtual void OnClean(EventArgs e)
        {
            EventHandler L_Handler = Clean;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Clean

        #region Init

        public event EventHandler Init;

        protected virtual void OnInit(EventArgs e)
        {
            EventHandler L_Handler = Init;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Init

        #region Initialized

        public event EventHandler Initialized;

        protected virtual void OnInitialized(EventArgs e)
        {
            EventHandler L_Handler = Initialized;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Initialized

        #region Load

        public event EventHandler Load;

        protected virtual void OnLoad(EventArgs e)
        {
            EventHandler L_Handler = Load;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Load

        #region Start

        public event EventHandler Start;

        protected virtual void OnStart(EventArgs e)
        {
            EventHandler L_Handler = Start;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Start

        #endregion Events

        #endregion Loading Process

        #region Activation

        #region Events

        #region Activated

        public event EventHandler Activated;

        protected virtual void OnActivated(EventArgs e)
        {
            EventHandler L_Handler = Activated;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Activated

        #region Desactivated

        public event EventHandler Desactivated;

        protected virtual void OnDesactivated(EventArgs e)
        {
            EventHandler L_Handler = Desactivated;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Desactivated

        #endregion Events

        protected virtual void ProcessActivate()
        {
            IsActive = true;
            StartInteraction();
            if (ValidationEnabled)
            {
                Validate(ValidationMode.SilentWarning);
            }
        }

        protected virtual void ProcessClose()
        {
            DoCleanup();
        }

        protected virtual void ProcessDesactivate()
        {
            IsActive = false;
            if (ValidationEnabled)
            {
                Validate(ValidationMode.SilentWarning);
            }
        }

        private void Activate_Execute(object P_Sender, CommandEventArgs e)
        {
            Log.InfoFormat("Activate command");

            ProcessActivate();

            OnActivated(EventArgs.Empty);
        }

        private void Close_Execute(object P_Sender, CommandEventArgs e)
        {
            Log.Debug("Close command in viewmodel");

            ProcessClose();
        }

        private void Desctivate_Execute(object P_Sender, CommandEventArgs e)
        {
            Log.InfoFormat("Desactivate command");

            ProcessDesactivate();

            OnDesactivated(EventArgs.Empty);
        }

        #endregion Activation

        #region Sub collections

        protected override BindableCollection<T> CreateCollection<T>(Func<T> adder)
        {
            BindableCollection<T> collection = base.CreateCollection<T>(adder);

            return collection;
        }

        protected override BindableCollection<T> CreateTrackedCollection<T>(Func<T> P_Adder)
        {
            BindableCollection<T> collection = base.CreateTrackedCollection<T>(P_Adder);

            unbinders.Add(() =>
            {
                collection.ItemChanged -= SubCollection_ItemChanged;
                collection.CollectionChanged -= SubCollection_CollectionChanged;
            });

            return collection;
        }

        #endregion Sub collections
    }
}